// Copyright (C) 2022 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
//
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<广西木子科技有限公司>，授权编号：<NT220402151538>，代码指纹：<A.648883738.885>，技术对接人微信：<ID: Lingmuziyi>】
// 【授权寄送：<收件：李先生、地址：南宁市科园西十路11号国电智能大厦1101F、电话：17736659550、邮箱：yingshashou@vip.qq.com>】
//
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
//
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rainbowchat.http.logic;

import java.util.*;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.eva.epc.common.util.CommonUtils;
import com.eva.framework.Processor;
import com.eva.framework.dbpool.DBDepend;
import com.eva.framework.dbpool.DBShell;
import com.eva.framework.dto.SysActionConst;
import com.eva.framework.utils.EndsExceptionFactory;
import com.eva.framework.utils.LoggerFactory;
import com.eva.framework.utils.SNHelper;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.x52im.rainbowchat.BaseConf;
import com.x52im.rainbowchat.cache.GroupsInfoCacheProvider;
import com.x52im.rainbowchat.cache.GroupsMembersCacheProvider;
import com.x52im.rainbowchat.cache.dto.GroupMember4Cache;
import com.x52im.rainbowchat.http.logic.dto.GroupEntity;
import com.x52im.rainbowchat.http.logic.dto.GroupMemberEntity;
import com.x52im.rainbowchat.im.impl.ChatLogicManager4Group;

/**
 * 群组相关的Http接口处理器实现类。
 *
 * @author Json
 * @since 4.3
 */
public class GroupChatProcessor implements SysActionConst, Processor {
    /**
     * 群组名称最大29个字符（中文算1个字符）
     */
    public final static int GROUP_NAME_MAX_LENGTH = 29;

    public static DBShell db = new DBShell();

    /**
     * 处理器的核心实现方法.<br>
     * 可据job_dispatch_id找到对应的JobDispatcher，JobDispatcher又能据
     * action_id进行对应动作的处理，以完成本次操作请求.
     *
     * @param job_dispatch_id 作业调度ID
     * @param action_id       动作ID
     * @param Object          newData 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
     * @param Object          oldData 客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
     * @param UserRoot        user 包含该请求用户完整个人信息封装对象（该对象来自于成功登陆后存放于session中的值）
     * @return 任意类型返回值
     * @throws Exception
     */
    @Override
    public Object process(int job_dispatch_id, int action_id, Object _newData, Object _oldData, User user) throws Exception {
        Object ret = null;
        String newDataJSON = (String) _newData;

        switch (job_dispatch_id) {
            // 处理服务端对外提供的群基本信息管理等
            case com.x52im.rainbowchat.http.logic.dto.JobDispatchConst.LOGIC_GROUP_BASE_MGR:
                ret = maintainMgrGroupBaseJobDispatcher(action_id, newDataJSON, _oldData, user);
                break;
            // 处理服务端对外提供的群组相关信息查询等
            case com.x52im.rainbowchat.http.logic.dto.JobDispatchConst.LOGIC_GROUP_QUERY_MGR:
                ret = maintainMgrGroupQueryJobDispatcher(action_id, newDataJSON, _oldData, user);
                break;
            default:
                throw EndsExceptionFactory.INVALID_JOB_DISPATCHER_ID(job_dispatch_id);
        }
        return ret;
    }

    /**
     * 维护群组基本信息管理.
     *
     * @param action_id 维护类型，SysConstant中的操作类型常量
     * @param newData   客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
     * @param oldData   客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
     * @param userName  操作者的用户名
     * @return 返回给客户端的对象
     */
    public String maintainMgrGroupBaseJobDispatcher(int action_id, String newDataJSON
            , Object oldData, User user) throws Exception {
        Vector sqls = new Vector(2);
        Vector values = new Vector(2);

        switch (action_id) {
            // 【接口1016-24-7】创建群组的接口
            case ACTION_APPEND1: {
                JSONObject nwObj = JSONObject.parseObject(newDataJSON);
                // 群主（实际就是本次群的创建者）
                final String ownerUid = nwObj.getString("owner_uid");
                // 群主昵称（实际就是本次群的创建者）
                final String ownerNickName = nwObj.getString("owner_nickname");
                // 群成员
                String membersJSON = nwObj.getString("members");

                LoggerFactory.getLog().debug("==【【【【【【【正在创建群。。。】】】】】】】==");

                GroupEntity newGroupInfoReturn = null;

                final ArrayList<GroupMemberEntity> members = new Gson().fromJson(membersJSON
                        , new TypeToken<ArrayList<GroupMemberEntity>>() {
                        }.getType());

                if (members.size() > 0) {
                    // 新建的群id号
                    String newGid = SNHelper.getSN("group_base_g_id");

                    sqls.add("INSERT INTO group_base(g_id,g_status,g_name" +
                            ",g_owner_user_uid,create_user_uid,create_time)" +
                            " VALUES(?,?,?,?,?," + DBDepend.getDefaultDatetimeFunc() + ")");
                    values.add(new Object[]{newGid, "1"
                            , constructNewGroupName(members)
                            , ownerUid
                            , ownerUid});

                    // 插入群成员
                    for (GroupMemberEntity m : members) {
                        prepareInsertMemberToGroup(sqls, values, m.getUser_uid(), newGid, ownerUid);
                    }

                    // 更新群成员人数
                    prepareUpdateGroupMemberCount(sqls, values, newGid);

                    // ** 提交数据库处理
                    LogicProcessor2.db.update(sqls, values, true);

//					1）写一个独立的方法，在建聊、删人、加人，且人数<=9时（写一个查询sql，取top9的人且头像文件不为空的情况），每次都重新生成建头像！
//					2）客户端每次都强制加载群头像（不要缓存了吧，省的麻类，像世界频道聊天一样，
//					       至少能保证及时拿到最新的群头像）！

                    // 将本次创建成功的群基本信息查询出来（将返回给客户端使用）
                    newGroupInfoReturn = this.queryGroupInfo(newGid, null);// 第2个参数null表示不需要取”我“在本群中的昵称，因为刚建的群还来不及设置群昵称呢

                    // ** 【生成群头像】：暂时未使用异步方式，原因是希望阻止用户频繁
                    //                   建群、生成头像，因为这样会导致更多的服务端性消耗
                    // 说明：放在通知前面生成头像，是希望在群员收到通知时，最新群头像早已生成完毕。
                    GroupAvatarHelper.generateGroupAvatar(newGid);

                    // ** 【高速缓存】：即时更新群缓存数据（因为新建了此群）
                    GroupsInfoCacheProvider.getInstance().reloadOneFromDB(newGid);

                    // ** 【实时通知本次群内的所有人】：群建立成功了
                    ChatLogicManager4Group.batchNotification4CreateGroupAsync(
                            ownerUid, ownerNickName, members, newGroupInfoReturn);

                    // ** 【高速缓存】：将新群成员数据更新到高速缓存中（以便后绪使用）
                    // TODO: 未来优化空间：直接用新群成员集合，可节省一次db查询，暂时可先不折腾
                    GroupsMembersCacheProvider.getInstance().reloadOneFromDB(newGid);
                }

                //** 返回值(返回值“0”表示本次建群失败！)
                return newGroupInfoReturn == null ? "0" : JSON.toJSONString(newGroupInfoReturn);
            }
            // 【接口1016-24-8】修改群名称
            case ACTION_APPEND2: {
                LoggerFactory.getLog().debug("==【【【【【【【正在修改群名。。。】】】】】】】==");
                JSONObject nwObj = JSONObject.parseObject(newDataJSON);
                String modify_by_uid = nwObj.getString("modify_by_uid");
                String modify_by_nickname = nwObj.getString("modify_by_nickname");
                String newGroupName = nwObj.getString("group_name");
                String gid = nwObj.getString("gid");

                sqls.add("UPDATE group_base SET g_name=? WHERE g_id=?");
                values.add(new Object[]{newGroupName, gid});
                boolean res = db.update(sqls, values, true);

                // ** 【高速缓存】：即时更新群缓存数据（因为新建了此群）
                GroupsInfoCacheProvider.getInstance().reloadOneFromDB(gid);

                // 【实时通知】：向群员发送一条通知（除修改者之外）
                ChatLogicManager4Group.batchNotification4GroupNameChangedAsync("修改群名"
                        , modify_by_uid, modify_by_nickname, newGroupName
//						, this.queryGroupMemberList(gid)
                        // 从高速缓存中读取群成员列表，提升性能
                        , GroupsMembersCacheProvider.getInstance().get(gid).values()
                        , gid);

                return res ? "1" : "0";// 1 表示更新成功，否则失败
            }
            // 【接口1016-24-9】修改"我"的群昵称
            case ACTION_APPEND3: {
                JSONObject nwObj = JSONObject.parseObject(newDataJSON);

                sqls.add("UPDATE group_members SET nickname_ingroup=? WHERE g_id=? and user_uid=?");
                values.add(new Object[]{nwObj.getString("nickname_ingroup")
                        , nwObj.getString("gid"), nwObj.getString("user_uid")});
                boolean res = db.update(sqls, values, true);

                return res ? "1" : "0";// 1 表示更新成功，否则失败
            }
            // 【接口1016-24-22】修改群公告（仅开放给群主） (增加开放给群管理员)
            case ACTION_APPEND4: {
                JSONObject nwObj = JSONObject.parseObject(newDataJSON);

                String g_notice_updateuid = nwObj.getString("g_notice_updateuid");
                String g_id = nwObj.getString("g_id");

                String ret = "-1";    // 返回值：未定义

                if (!queryGroupOwner(g_id, g_notice_updateuid)) {
                    ret = "2";        // 返回值：修改人已不是群主，本次修改失败！
                } else if (isAdmin(g_id, g_notice_updateuid)) {
                    ret = "3";  // 返回值：修改人不是管理员，本次修改失败
                } else {
                    sqls.add("UPDATE group_base SET g_notice=?,g_notice_updatetime=" + DBDepend.getDefaultDatetimeFunc()
                            + ",g_notice_updateuid=? WHERE g_id=? and g_owner_user_uid=?");
                    values.add(new Object[]{nwObj.getString("g_notice"), g_notice_updateuid, g_id, g_notice_updateuid});
                    boolean res = db.update(sqls, values, true);

                    if (res) {
                        ret = "1";    // 返回值：本次修改成功
                    }
                }

                return ret;
            }
            // 【接口1016-24-23】删除群成员或退群 接口
            case ACTION_APPEND5: {
                boolean sucess = false;
                try {
                    LoggerFactory.getLog().debug("==【【【【【【【正在删除群成员或退群。。。】】】】】】】==");

                    JSONObject nwObj = JSONObject.parseObject(newDataJSON);
                    // 本次删除的操作人uid（群主踢人时本参数为群主，如果是用户自已退出退路时本参数为退出者自已）
                    final String del_opr_uid = nwObj.getString("del_opr_uid");
                    // 本次删除的操作人昵称
                    final String del_opr_nickname = nwObj.getString("del_opr_nickname");
                    // 本次删除发生的群id
                    final String gid = nwObj.getString("gid");
//					// 本次删除发生的群名称
//					final String gname = nwObj.getString("gname");
                    // 要删除的群成员
                    String membersJSON = nwObj.getString("members");

                    GroupEntity newGroupInfoReturn = null;

                    final ArrayList<ArrayList> membersBeDelete = new Gson().fromJson(membersJSON
                            , new TypeToken<ArrayList<ArrayList>>() {
                            }.getType());

//					ArrayList<ArrayList> membersBeDelete = new Gson().fromJson(newDataJSON
//							, new TypeToken<ArrayList<ArrayList>>(){}.getType());

                    // 要被删除的群员（用GroupMemberEntity组织的集合，调用其它方法时好用一些）
                    ArrayList<GroupMemberEntity> membersBeDeleteList = new ArrayList<GroupMemberEntity>();
//
                    if (membersBeDelete != null && membersBeDelete.size() > 0) {
                        for (ArrayList row : membersBeDelete) {
                            // 以下字段及顺序请确保与客户端的接口请求保持一致！
//							String gid = (String)row.get(0);
                            String userId = (String) row.get(1);
                            String nickName = (String) row.get(2);

                            // 将群员的基本信息存到一个集合中备用
                            GroupMemberEntity gme = new GroupMemberEntity();
                            gme.setUser_uid(userId);
                            gme.setNickname(nickName);
                            membersBeDeleteList.add(gme);

                            sqls.add("delete from group_members where g_id=? and user_uid=? " +
                                    //## Bug FIX 20190330 START:
                                    //## 强制检查被删除者是否是群主，如果是群主，应该由它使用“解散群”而不是“退群”功能，否则上
                                    //## 会出现群主已经从群里退出的数据不一致情况（这种情况多发生于群主已转给“我”，但界面数据没
                                    //## 来的及刷新的情况下，以为还是普通群员，而点了“退群”按钮时）
                                    "	and user_uid <> (SELECT g_owner_user_uid FROM group_base WHERE g_id=?) ");
                            //## Bug FIX 20190330 END:

                            values.add(new Object[]{gid, userId, gid});
                        }
                    }

                    // 更新群成员人数
                    prepareUpdateGroupMemberCount(sqls, values, gid);

                    db.update(sqls, values, false);
                    sucess = true;

                    // ** 【生成群头像】：暂时未使用异步方式，原因是希望阻止用户频繁
                    //                   删除群员并生成头像，因为这样会导致更多的服务端性消耗
                    // 说明：放在通知前面生成头像，是希望在群员收到通知时，最新群头像早已生成完毕。
                    GroupAvatarHelper.generateGroupAvatar(gid);

                    // ** 【高速缓存】：将最新群成员数据更新到高速缓存中（以便后绪使用）
                    // TODO: 未来优化空间：直接要删除的集合来更新缓存，可节省一次db查询，暂时可先不折腾
                    GroupsMembersCacheProvider.getInstance().reloadOneFromDB(gid);

                    // 【实时通知】：通知此次被删除的人、通知群里的其他余下的人
                    ChatLogicManager4Group.batchNotification4SomeoneBeRemovedAsync(
                            "删除群员", del_opr_uid, del_opr_nickname
                            , membersBeDeleteList
//							, this.queryGroupMemberList(gid)
                            // 从高速缓存中读取群成员列表，提升性能
                            , GroupsMembersCacheProvider.getInstance().get(gid).values()
                            , gid
//							, gname
                    );
                } catch (Exception e) {
                    LoggerFactory.getLog().debug(e.getMessage(), e);
                }

                return sucess ? "1" : "0";// 1 表示更新成功，否则失败
            }
            // 【接口1016-24-24】邀请入群接口
            case ACTION_APPEND6: {
                LoggerFactory.getLog().debug("==【【【【【【【正在邀请入群群。。。】】】】】】】==");

                JSONObject nwObj = JSONObject.parseObject(newDataJSON);
                String inviteUid = nwObj.getString("invite_uid");          // 邀请发起人的uid
                String inviteNickName = nwObj.getString("invite_nickname");// 邀请发起人的昵称
                String inviteToGid = nwObj.getString("invite_to_gid");             // 邀请至群id
                String membersBeInviteJSON = nwObj.getString("members");   // 被邀请的成员

                // 新加群员列表
                ArrayList<GroupMemberEntity> newJoinedMemberList = new ArrayList<GroupMemberEntity>();
                // 老群员列表
                Collection<GroupMember4Cache> oldMemberList = new ArrayList<GroupMember4Cache>();

                ArrayList<ArrayList> membersBeInvite = new Gson().fromJson(membersBeInviteJSON
                        , new TypeToken<ArrayList<ArrayList>>() {
                        }.getType());
                if (!CommonUtils.isStringEmpty(inviteUid)
                        && membersBeInvite != null
                        && membersBeInvite.size() > 0) {
                    // 查询老群员列表备用
//					oldMemberList = queryGroupMemberList(inviteToGid);
                    // 从高速缓存中读取群成员列表，提升性能
                    oldMemberList = GroupsMembersCacheProvider.getInstance()
                            .get(inviteToGid).values();

                    // 插入新群员
                    for (ArrayList row : membersBeInvite) {
                        // 以下字段及顺序请确保与客户端的接口请求保持一致！
                        String gid = (String) row.get(0);
                        String userId = (String) row.get(1);
                        String nickname = (String) row.get(2);

                        // 将群员的基本信息存到一个集合中备用
                        GroupMemberEntity newGme = new GroupMemberEntity();
                        newGme.setUser_uid(userId);
                        newGme.setNickname(nickname);
                        newJoinedMemberList.add(newGme);

                        // 如果本次要邀请的人确实不在db中，则加入之（否则无需重复加入）
                        if (db.queryData("select 1 from group_members where g_id='"
                                + gid + "' and user_uid=" + userId).size() <= 0) {
                            prepareInsertMemberToGroup(sqls, values, userId, gid, inviteUid);
                        }
                    }
                }

                // 更新群成员人数
                prepareUpdateGroupMemberCount(sqls, values, inviteToGid);

                boolean res = db.update(sqls, values, false);

                // ** 【生成群头像】：暂时未使用异步方式，原因是希望阻止用户频繁
                //                   邀请群员并生成头像，因为这样会导致更多的服务端性消耗
                // 说明：放在通知前面生成头像，是希望在群员收到通知时，最新群头像早已生成完毕。
                GroupAvatarHelper.generateGroupAvatar(inviteToGid);

                // 【实时通知】：通知新加入的人（“他”加群成功）、老群员（有人加群了）
                ChatLogicManager4Group.batchNotification4InviteMembersAsync("群聊CMD-邀请通知"
                        , inviteUid, inviteNickName
                        , newJoinedMemberList
                        , oldMemberList
                        , this.queryGroupInfo(inviteToGid, null));

                // ** 【高速缓存】：将最新群成员数据更新到高速缓存中（以便后绪使用）
                // TODO: 未来优化空间：直接用新进成员集合，可节省一次db查询，暂时可先不折腾
                GroupsMembersCacheProvider.getInstance().reloadOneFromDB(inviteToGid);

                return res ? "1" : "0";// 1 表示更新成功，否则失败
            }

            // 【接口1016-24-25】转让本群（仅开放给群主）接口
            case ACTION_APPEND7: {
                LoggerFactory.getLog().debug("==【【【【【【【正在转让群。。。】】】】】】】==");

                JSONObject nwObj = JSONObject.parseObject(newDataJSON);
                String oldOwnerUid = nwObj.getString("old_owner_uid");           // 原群主uid
//				String oldOwnerNickname = nwObj.getString("old_owner_nickname"); // 原群主昵称
                String newOwnerUid = nwObj.getString("new_owner_uid");           // 新群主uid（即将被转让为群主）
                String newOwnerNickname = nwObj.getString("new_owner_nickname"); // 新群主昵称
                String gid = nwObj.getString("g_id");                   // 转让发生的群
//				String gname = nwObj.getString("g_name");                   // 转让发生的群名

                String ret = "-1";    // 返回值：未定义

                if (!queryGroupOwner(gid, oldOwnerUid)) {
                    ret = "2";        // 返回值：转让发起人已不是群主，本次转让失败！
                } else {
                    if (db.queryData("select 1 from group_members where user_uid='"
                            + newOwnerUid + "' and g_id='" + gid + "'").size() <= 0) {
                        ret = "3";    // 返回值：被转让人不是群成员，本次转让失败！
                    } else {
                        // 设置新群主
                        sqls.add("update group_base set g_owner_user_uid=? where g_id=? and g_owner_user_uid=?");
                        values.add(new Object[]{newOwnerUid, gid, oldOwnerUid});

                        // 更新新群主成为群主的时间
                        sqls.add("update group_members set be_owner_time=" + DBDepend.getDefaultDatetimeFunc()
                                + " where g_id=? and user_uid=?");
                        values.add(new Object[]{gid, newOwnerUid});

                        boolean res = db.update(sqls, values, false);
                        if (res) {
                            ret = "1"; // 返回值：本次转让成功
                        }

                        // 【实时通知】：向群员发送一条通知（除修改者之外）
                        ChatLogicManager4Group.batchNotification4CommonInfoAsync("转让群主"
                                , "\"" + newOwnerNickname + "\"已成为新群主"
//								, this.queryGroupMemberList(gid)
                                // 从高速缓存中读取群成员列表，提升性能
                                , GroupsMembersCacheProvider.getInstance().get(gid).values()
                                , gid
//								, gname
                                // 原群主（也就是本次转让发起人）就不需要通知它自已了
                                , oldOwnerUid
                        );
                    }
                }

                return ret;
            }
            // 【接口1016-24-26】解散群（仅开放给群主）接口
            case ACTION_APPEND8: {
                LoggerFactory.getLog().debug("==【【【【【【【正在解散群。。。】】】】】】】==");

                JSONObject nwObj = JSONObject.parseObject(newDataJSON);
                String ownerUid = nwObj.getString("owner_uid");           // 群主uid(也就是解散的发起人)
                String ownerNickname = nwObj.getString("owner_nickname"); // 群主昵称
                String gid = nwObj.getString("g_id");                     // 将要被解散的群
//				String gname = nwObj.getString("g_name");                 // 将要被解散的群名

                String ret = "-1"; // 返回值：未定义

                if (!queryGroupOwner(gid, ownerUid)) {
                    ret = "2";     // 返回值：解散发起人已不是群主，本次解散失败！
                } else {
                    try {
                        // 群解散前先取出所有群员列表（以备通知）
                        //					ArrayList<GroupMember4Cache> allMembersBeforeDismiss = this.queryGroupMemberList(gid);
                        // 从高速缓存中读取群成员列表，提升性能
                        Collection<GroupMember4Cache> allMembersBeforeDismiss =
                                GroupsMembersCacheProvider.getInstance().get(gid).values();

                        //** 先存档该群所有未超期的聊天记录（已超期的会自动存档哦）
                        if (db.queryData("select 1 from missu_user_msgs_collect where chat_type=2 and dest_uid="
                                + gid).size() > 0) {
                            LoggerFactory.getLog().debug("[群解解散时]该群存在未被存档的聊天记录，解散群时也将一并转储之...");

                            // 先将聊天记录插入到存档表中
                            sqls.add(TimmingRestoreMessages.COPY_CHATTING_MESSAGES_TO_ACHIVED_SQL + " where chat_type=2 and dest_uid=?");
                            values.add(new Object[]{gid});

                            // 再将聊天记录从聊天记录表中移除
                            sqls.add(TimmingRestoreMessages.DELETE_CHATTING_MESSAGES_SQL + " where chat_type=2 and dest_uid=?");
                            values.add(new Object[]{gid});
                        }

                        //** 先删除所有未被收取的本群离线消息（微信也是这样的）
                        if (db.queryData("select 1 from missu_offline_history where group_id='"
                                + gid + "'").size() > 0) {
                            LoggerFactory.getLog().debug("[群解解散时]该群存在未被拉取的离线消息，解散群时也将一并删除之...");

                            sqls.add("delete from missu_offline_history where group_id=?");
                            values.add(new Object[]{gid});
                        }

                        //** 再删除所有成员
                        sqls.add("delete from group_members where g_id=?");
                        values.add(new Object[]{gid});

                        //** 再更新群基本信息中的状态字段
                        // 补充说明：群解散时，将删除群成员、群友没有收取的离线聊天记录（微信也是这样），
                        //          但不会删除群基本信息（只设状态为-1)，因为解散群的离线通知、公司
                        //          的后台运营时，可能需要群的基本信息，用于提供基顾数据，所以不应把群
                        //          基本信息也删除了。
                        //					sqls.add("delete from group_base where g_id=?");
                        sqls.add("update group_base set g_status=-1,del_user_uid=?,del_time=" + DBDepend.getDefaultDatetimeFunc()
                                + " where g_id=?");
                        values.add(new Object[]{ownerUid, gid});

                        // 更新群成员人数
                        prepareUpdateGroupMemberCount(sqls, values, gid);

                        boolean res = db.update(sqls, values, true);
                        if (res) {
                            ret = "1"; // 返回值：本次解散成功
                        }

                        // 【实时通知】：通知除群主外的所有人
                        ChatLogicManager4Group.batchNotification4GroupDismissedAsync("群被解散"
                                , allMembersBeforeDismiss, gid
                                //							, gname
                                , ownerUid, ownerNickname);

                        // ** 【高速缓存】：从高速缓存中删除此群成员数据
                        //					GroupsMembersCacheProvider.getInstance().reloadGroupFromDB(gid);
                        GroupsMembersCacheProvider.getInstance().remove(gid);

                        // ** 【高速缓存】：从高速缓存中删除此群（因为解散了此群）
                        //					GroupsInfoCacheProvider.getInstance().reloadOneFromDB(gid);
                        //# TODO: 为解决群解散时，离线APNS推送逻辑不能走完的bug而暂时注释了以下代码。
						/* 为了让上方离线消息的APNS推送代码更内聚，推送时需要的群名称等基本信息，
						是实时查高速缓存实现的，这样做的目的是提升离线消息处理代码的调用简单性。
						但带来的问题是，当以下代码执行时（即从高速缓存中去掉已解散的群基本信息），
						离线APNS推送逻辑就没法正常走完了（因为需要的群基本信息已不能从高速缓存中
						查到）。那么长此以往，被解散群的基本信息就留在内存里（直到下次重启）。
						解决办法可以是：为此缓存加一个解散时间，可以设定多长时间后，由单独的定时器
						来清楚，这样更合理。但实际上不清理，也占用不了多少内存，。单独维护清理逻辑
						反而得不偿失，可以先按现在的逻辑实现吧，以后进行高效架构拆分时再处理不迟。*/
//						GroupsInfoCacheProvider.getInstance().remove(gid);// comment by Jack Jiang 20190228
                    } catch (Exception e) {
                        LoggerFactory.getLog().warn("发生在解散群时：", e);
                    }
                }

                return ret;
            }

            // 设置某个成员成为群管理员
            case ACTION_APPEND9: {
                return MgrACTION_APPEND9(newDataJSON);
            }
            // 删除群管理员
            case ACTION_APPEND10: {
                return MgrACTION_APPEND10(newDataJSON);
            }

            default:
                throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
        }
    }

    private String MgrACTION_APPEND10(String newDataJSON) throws Exception {
        JSONObject nwObj = JSONObject.parseObject(newDataJSON);
        String gId = nwObj.getString("gId");    // 群ID
        String ownerUserId = nwObj.getString("ownerUserId");    // 群主ID
        String userUid = nwObj.getString("userUid");    // 被邀请成为管理员的成员ID

        boolean b = updateAdmin2Member(gId, userUid);
        return b ? "1" : "0";
    }

    private String MgrACTION_APPEND9(String newDataJSON) throws Exception {
        JSONObject nwObj = JSONObject.parseObject(newDataJSON);
        String gId = nwObj.getString("gId");    // 群ID
        String ownerUserId = nwObj.getString("ownerUserId");    // 群主ID
        String userUid = nwObj.getString("userUid");    // 被邀请成为管理员的成员ID

        if (!queryGroupOwner(gId, ownerUserId)) {
            return "该ownerUserId已经不是群主";
        }

        List<Map<String, Object>> members = queryGroupMember(gId, userUid);
        if (members == null || members.isEmpty()) {
            return "用户ID不是群成员";
        }
        for (int i = 0; i < members.size(); i++) {
            Map<String, Object> member = members.get(i);
            String isAdmin = (String) member.get("is_admin");
            String userUid1 = (String) member.get("user_uid");
            if (isAdmin != null && isAdmin.equals("1")) {
                return userUid1 + "已经是群管理员,不能重复设置";
            }
        }

        int adminNum = queryGroupAdminNum(gId);
        int maxAdmin = BaseConf.getInstance().getGROUP_MAX_ADMIN();
        if (adminNum + members.size() > maxAdmin) {
            return "群管理员最多只能有" + maxAdmin + "个";
        }

        boolean b = updateGroupMember2Admin(gId, userUid);
        return b ? "1" : "0";
    }

    /**
     * 维护群组信息查询管理.
     *
     * @param action_id 维护类型，SysConstant中的操作类型常量
     * @param newData   客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
     * @param oldData   客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
     * @param userName  操作者的用户名
     * @return 返回给客户端的对象
     */
    public String maintainMgrGroupQueryJobDispatcher(int action_id, String newDataJSON
            , Object oldData, User user) throws Exception {
        Vector sqls = new Vector(2);
        Vector values = new Vector(2);

        switch (action_id) {
            // 【接口1016-25-7】获取用户群列表的接口
            case ACTION_APPEND1: {
                // 客户端传过来的是UID
                String uid = newDataJSON;
//				// 该用户的好友群列表
                ArrayList<GroupEntity> groupsToClient = queryGroupInfoList(uid);

                return JSON.toJSONString(groupsToClient);
            }
            // 【接口1016-25-8】查询群基本信息
            case ACTION_APPEND2: {
                JSONObject nwObj = JSONObject.parseObject(newDataJSON);

                // 查询的群id
                String gid = nwObj.getString("gid");
                // 非必须参数，如果本参数不为空，则表示要同时把”我“在该群中的昵称给查出来，否则不需要查
                String myUserId = nwObj.getString("my_user_id");

                return JSON.toJSONString(queryGroupInfo(gid, myUserId));
            }
            // 【接口1016-25-9】查询群成员列表（含成员基本信息）
            case ACTION_APPEND3: {
                // 查询的群id
                String gid = newDataJSON;
                return JSON.toJSONString(queryGroupMemberList(gid, null));
            }
            // 查询群成员信息
            case ACTION_APPEND4: {
                JSONObject nwObj = JSONObject.parseObject(newDataJSON);
                String gid = nwObj.getString("gId");
                String userId = nwObj.getString("userUid");
                return JSON.toJSONString(queryGroupMemberList(gid, userId));
            }
            default:
                throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
        }
    }

    private String constructNewGroupName(ArrayList<GroupMemberEntity> members) {
        if (members.size() > 0) {
            String name = "";

            int cnt = 0;
            for (int i = members.size() - 1; i >= 0; i--) {
                name += (cnt == 0 ? "" : "、") + members.get(i).getNickname();
                cnt += 1;

                // 最多只合成3个人的昵称作为群名
                if (cnt >= 3) {
                    break;
                }
            }

            System.out.println("【截断前】生成完成的群聊名称：" + name);
            name = CommonUtils.truncString(name, GROUP_NAME_MAX_LENGTH, "...");
            System.out.println("【截断后】生成完成的群聊名称：" + name);

            return name + "等";
        }

        return "未命名的群聊";
    }

    /**
     * 查询指定群组的群成员列表。
     * <p>
     * <b>注意：</b>如果用于大量频繁读取且只需要群成员id的场景下，请使用
     * {@link GroupsMembersCacheProvider#get(String)}缓存，从而提升性能，
     * 防止频繁读取DB这种慢IO拖慢系统性能且有可能导致DB瓶颈。
     *
     * @param gid
     * @param userUid 传了该字段则只查这个ID的群成员信息
     * @see GroupsMembersCacheProvider#get(String)
     */
    public static <T extends GroupMember4Cache> ArrayList<T> queryGroupMemberList(String gid, String userUid) throws Exception {
        StringBuilder sb = new StringBuilder()
                .append("select ")
                .append("	a.user_uid ")
                .append("	,a.g_id ")
                .append("	," + DBDepend.getNVLFunc("a.nickname_ingroup", "b.nickname") + " as nname ")
                .append("	,b.user_avatar_file_name,a.is_admin ")
                .append(" FROM ")
                .append("(")
                // 使用子查询提升查询性能
                .append("	select user_uid,g_id,nickname_ingroup,is_admin from group_members  where g_id='" + gid + "' ")
                .append(userUid != null ? " and user_uid=" + userUid + " " : "")
                .append(") a ")
                .append("left join missu_users b on a.user_uid=b.user_uid ")
                // 本条件将限制只能查到正常状态下的群成员列表（使用EXISTS语句作为条件是为了提升查询性能）
                .append("where EXISTS (select 1 from group_base c where a.g_id=c.g_id and c.g_status=1) ");

        // 群员列表数据库行数据
        Vector<Vector> members = db.queryData(sb.toString());
        ArrayList<GroupMember4Cache> membersToClient = new ArrayList<GroupMember4Cache>();
        // 遍历行数据，组装成对象List
        for (Vector row : members) {
            GroupMemberEntity gme = new GroupMemberEntity();
            int i = 0;
            gme.setUser_uid((String) row.get(i++));
            gme.setG_id((String) row.get(i++));
            gme.setNickname((String) row.get(i++));
            gme.setUserAvatarFileName((String) row.get(i++));
            gme.setIs_admin((String) row.get(i++));

            membersToClient.add(gme);
        }

        return (ArrayList<T>) membersToClient;
    }

    /**
     * 查询指定用户的所有群列表。
     * <p>
     * 注意：本方法中的sql查询字段个数、顺序，必须与解析方法 {@link #constructGroupEntityFromQueryRow(Vector)}
     * 保持一致，否则无法完成从数据库原始行数据到 GroupEntity 对象的转换！
     *
     * @param uid 被查询的用户id
     * @return
     * @throws Exception
     * @see {@link #constructGroupEntityFromQueryRow(Vector)}
     */
    private ArrayList<GroupEntity> queryGroupInfoList(String uid) throws Exception {
        StringBuilder sb = new StringBuilder();
        // 查询指定用户的群列表
        sb.append("select ")
                .append("	a.g_id")              // 群id
                .append("	,b.g_status")         // 群状态
                .append("	,b.g_name")           // 群名称
                .append("	,b.g_owner_user_uid") // 群主uid
                .append("	,b.g_notice")         // 群公告
//			.append("	,b.msg_no_promt" )     // 免打扰状态
                .append("	,b.max_member_count") // 允许的最大群员数
                .append("	,b.g_member_count")   // 当前总群员数
                .append("	,c.nickname ")        // 群主昵称
                .append("	,a.nickname_ingroup ")// "我"在本群的昵称

                .append("	,b.g_notice_updatetime ")// 群公告更新时间
                .append("	,b.g_notice_updateuid ") // 群公告更新者
                .append("	,d.nickname as nickname_ingroup ")   // 群公告更新者昵称
                .append("	,b.create_time ")        // 群创建时间
                .append("	,1 as isInGroup ")       // ”我“是否在此群中（既然是查我的群列表，那肯定在了，加此字段是为了与 queryGroupInfo()方法中的查询保持一致
                .append("	,e.nickname as create_user_name ")    // 群创建者昵称
                .append("from ")
                .append("( ")
                .append("	select user_uid,g_id,nickname_ingroup" +
                        "	 from group_members where user_uid=" + uid)
                .append(")a  ")
                // 注意：g_status=1表示此群当前是有效的（另外的状态是删除或被封禁，就不需要返回给用户了）
                .append("join group_base b on a.g_id=b.g_id and b.g_status=1 ")
                .append("left join missu_users c on b.g_owner_user_uid=c.user_uid ")
                .append("left join missu_users d on b.g_notice_updateuid=d.user_uid ")
                .append("left join missu_users e on b.create_user_uid=e.user_uid ")
                .append("order by b.create_time desc ");
        ;

        // 该用户的好友群列表
        Vector<Vector> groups = db.queryData(sb.toString());
        ArrayList<GroupEntity> groupsToClient = new ArrayList<GroupEntity>();
        // 遍历群信息列表，组装成对象List
        for (Vector row : groups) {
            groupsToClient.add(constructGroupEntityFromQueryRow(row));
        }

        return groupsToClient;
    }

    /**
     * 查询指定群的基本信息。
     * <p>
     * 注意：本方法中的sql查询字段个数、顺序，必须与解析方法 {@link #constructGroupEntityFromQueryRow(Vector)}
     * 保持一致，否则无法完成从数据库原始行数据到 GroupEntity 对象的转换！
     *
     * @param gid      要查询的群id
     * @param myUserId 非必须参数，如果本参数不为空则表示要同时把”我“在该群中的昵称等给查出来，否则不需要查（表示普通的查询群信息）
     * @return
     * @throws Exception
     * @see {@link #constructGroupEntityFromQueryRow(Vector)}
     */
    private GroupEntity queryGroupInfo(String gid, String myUserId) throws Exception {
        // 是否需要取出”我“在该群中的信息（比如：群内昵称、我是否在此群中）
        boolean needGetMyselfInfo = !CommonUtils.isStringEmpty(myUserId);

        // 取出”我“在群里的昵称子查询
        String nickname_ingroup_SQLFeild = ",(select nickname_ingroup from group_members " +
                "	where g_id='" + gid + "' and user_uid='" + myUserId + "') as nickname_ingroup ";
        // 取出”我“是否在此群里的子查询（存在则返回1，不存在则返回0，返回-1表示不需要查询此值）
        String im_in_group_SQLField = "," + DBDepend.getNVLFunc(
                "(select 1 from group_members where g_id='" + gid + "' and user_uid='" + myUserId + "')", "0")
                + " as isInGroup ";

        StringBuilder sb = new StringBuilder();
        sb.append("select b.g_id")           // 群id
                .append("	,b.g_status")        // 群状态
                .append("	,b.g_name")          // 群名称
                .append("	,b.g_owner_user_uid")// 群主uid
                .append("	,b.g_notice")        // 群公告
//			.append("	,b.msg_no_promt")    // 免打扰状态
                .append("	,b.max_member_count")// 允许的最大群员数
                .append("	,b.g_member_count ") // 当前总群员数
                .append("	,c.nickname ")       // 群主昵称
                // "我"在本群的昵称
                .append(needGetMyselfInfo ? nickname_ingroup_SQLFeild : "	,null as  nickname_ingroup ")

                .append("	,b.g_notice_updatetime ")// 群公告更新时间
                .append("	,b.g_notice_updateuid ") // 群公告更新者
                .append("	,d.nickname as nickname_ingroup ")   // 群公告更新者昵称
                .append("	,b.create_time ")        // 群创建时间
                // 取出”我“是否在此群里的子查询（存在则返回1，不存在则返回0，返回-1表示不需要查询此值）
                .append(needGetMyselfInfo ? im_in_group_SQLField : ",-1 as isInGroup ")
                .append("	,e.nickname as create_user_name ")    // 群创建者昵称
                .append("from ")
                .append("(")
                .append("	select * from group_base where g_id='" + gid + "' and g_status=1 ")
                .append(")b ")
                .append("left join missu_users c on b.g_owner_user_uid=c.user_uid ")
                .append("left join missu_users d on b.g_notice_updateuid=d.user_uid ")
                .append("left join missu_users e on b.create_user_uid=e.user_uid ");

        Vector<Vector> vv = db.queryData(sb.toString());
        if (vv.size() > 0) {
            return constructGroupEntityFromQueryRow(vv.get(0));
        }

        return null;
    }

    /**
     * 解析数据库中查询出的群基本信息，并组装成对象，方便使用。
     * <p>
     * 注意：本方法中对数据库原始行数据的解析顺序、字段意义的改动，直接会影响到
     * 方法 {@link #queryGroupInfo(String)}、{@link #queryGroupInfoList(String)}
     * 期望的数据结果。所以如果改动本方法，则一定要对应的改动这两个方法！
     *
     * @param row
     * @return
     * @see #queryGroupInfo(String)
     * @see #queryGroupInfoList(String)
     */
    private GroupEntity constructGroupEntityFromQueryRow(Vector row) {
        GroupEntity gee = new GroupEntity();
        gee.setG_id((String) row.get(0));
        gee.setG_status((String) row.get(1));
        gee.setG_name((String) row.get(2));
        gee.setG_owner_user_uid((String) row.get(3));
        gee.setG_notice((String) row.get(4));
//		gee.setMsg_no_promt((String)row.get(5));
        gee.setMax_member_count((String) row.get(5));
        gee.setG_member_count((String) row.get(6));
        gee.setG_owner_name((String) row.get(7));
        gee.setNickname_ingroup((String) row.get(8));

        gee.setG_notice_updatetime((String) row.get(9));
        gee.setG_notice_updateuid((String) row.get(10));
        gee.setG_notice_updatenick((String) row.get(11));

        gee.setCreate_time((String) row.get(12));
        gee.setImIsInGroup((String) row.get(13));
        gee.setCreate_user_name((String) row.get(14));

        return gee;
    }

    /**
     * 加群预编译SQL.
     *
     * @param sqls
     * @param values
     * @param uidBeInsert 被加入或邀请人UI
     * @param gidBeInsert 被加入的群
     * @param inviteUid   邀请加群的发起人
     */
    private static void prepareInsertMemberToGroup(Vector sqls, Vector values
            , String uidBeInsert, String gidBeInsert, String inviteUid) {
        sqls.add("INSERT INTO group_members(user_uid,g_id,join_time,be_invite_user_id,msg_time_start" +
                ") VALUES(?,?," + DBDepend.getDefaultDatetimeFunc() + ",?," + DBDepend.getDefaultDatetimeFunc() + ")");
        values.add(new Object[]{uidBeInsert, gidBeInsert, inviteUid});
    }

    /**
     * 更新群内人数预编译SQL.
     *
     * @param sqls
     * @param values
     * @param gid    要更新人数的群id
     */
    private static void prepareUpdateGroupMemberCount(Vector sqls, Vector values, String gid) {
        sqls.add("update group_base set g_member_count=" +
                "(select count(*) from group_members where g_id=?) where g_id=?");
        values.add(new Object[]{gid, gid});
    }

    /**
     * 更新群成员为管理员
     *
     * @param gId
     * @param userUid
     */
    private static boolean updateGroupMember2Admin(String gId, String userUid) throws Exception {
        boolean b = db.update(Collections.singletonList("update group_members set is_admin='1', be_admin_time=now() where g_id=" + gId + " and user_uid in (" + userUid + ")"), true);
        return b;
    }

    /**
     * 更新群成员为管理员
     *
     * @param gId
     * @param userUid
     */
    private static boolean updateAdmin2Member(String gId, String userUid) throws Exception {
        boolean b = db.update(Collections.singletonList("update group_members set is_admin=null, be_admin_time=null where g_id=" + gId + " and user_uid in (" + userUid + ")"), true);
        return b;
    }

    /**
     * 获取群成员信息
     *
     * @param gId
     * @param userUid
     * @return
     * @throws Exception
     */
    private static List<Map<String, Object>> queryGroupMember(String gId, String userUid) throws Exception {
        List<Map<String, Object>> members = db.queryDataListMap("select * from group_members where g_id='" + gId + "' and user_uid in (" + userUid + ")");
        return members;
    }

    /**
     * 获取群管理员人数
     *
     * @param gId
     * @return
     * @throws Exception
     */
    private static int queryGroupAdminNum(String gId) throws Exception {
        Vector<Vector> vs = db.queryData("select count(1) from group_members where g_id='" + gId + "' and is_admin='1'");
        String num = "0";
        for (Vector row : vs) {
            num = (String) row.get(0);
        }
        return Integer.parseInt(num);
    }

    /**
     * 判断该成员是否是群主或者管理员
     *
     * @param gId
     * @param userUid
     * @return
     * @throws Exception
     */
    private static boolean isAdmin(String gId, String userUid) throws Exception {
//        String sql = "select 1 from group_base a where a.g_id =? and a.g_owner_user_uid =? " +
//                "union " +
//                "select 1 from group_members b where b.g_id =? and b.user_uid =? and b.is_admin ='1'";
        String sql = "select 1 from group_members b where b.g_id =? and b.user_uid =? and b.is_admin ='1'";
        Vector<Vector> vs = db.queryData(sql, new Object[]{gId, userUid});
        return vs.isEmpty() ? false : true;
    }

    /**
     * 从数据库中查询指定用户是否是该群群主。
     *
     * @param gid           被查询的群
     * @param theUidBeQuery 被查询的人
     * @return
     */
    private static boolean queryGroupOwner(String gid, String theUidBeQuery) throws Exception {
        return db.queryData("select 1 from group_base where g_owner_user_uid='"
                + theUidBeQuery + "' and g_id='" + gid + "'").size() > 0;
    }
}
